broker: add storage read/write and live-signal actions to the contract - #6922
Open
jrobotham-square wants to merge 2 commits into
Open
broker: add storage read/write and live-signal actions to the contract#6922jrobotham-square wants to merge 2 commits into
jrobotham-square wants to merge 2 commits into
Conversation
Memory read/write graduates from a deferred operation into v1. A keyless agent
could address an encrypted-memory record (storage.address) but not read or
write one, so a keyless runtime stays amnesiac across wakes.
Both actions are slug-addressed and mirror the existing nine:
- storage.get { slug } -> { value? } (value absent = no record, not an error)
- storage.put { slug, value } -> EventPublished
The host derives the address, encrypts on put, and decrypts on get, so the
secret never leaves the key holder -- the same reason storage.address routes
through the interface. value is non-empty and bounded by MAX_CONTENT_BYTES. No
patch/rm: a client composes those from read-modify-write, as profile.set
already does for partial updates.
storage.address is left in place; whether get/put make it redundant is an open
question for review.
Contract crate only. buzz-cli's exhaustive matches gain the two variants when
the keyless-client branch rebases onto this.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Joel Robotham <jrobotham@squareup.com>
jrobotham-square
marked this pull request as draft
August 27, 2026 06:50
…ontract Give a keyless agent parity with a local one for the ephemeral signals a running agent emits so an owner and channel can see it work. presence.set and typing.set were named as deferred by #6742; observer.emit and liveness.ping are net-new -- the trajectory and keepalive planes the contract never enumerated but a keyless agent needs just as much once it holds no relay connection. Four best-effort actions, all following the existing contract shape (strict wire, one spelling of every identity, no member names its own subject): - presence.set -> status only (reuses buzz_core PresenceStatus) - typing.set -> channelId only; ephemeral, no stop counterpart - observer.emit -> a batch of frames, each { kind, payload }; payload is opaque and encrypted host-side, and the outcome is a batch receipt since re-batched frames have no stable per-frame id - liveness.ping -> { channelId, turnId }; distinct from an observer frame so a host can attach meaning (reset a stall watchdog), not just forward it The host still derives owner, key, encryption, and all Nostr metadata; the agent supplies only content. observer.emit and liveness.ping overlap on the wire -- flagged in the module docs so a reviewer can collapse liveness.ping if preferred. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Joel Robotham <jrobotham@squareup.com>
jrobotham-square
force-pushed
the
jrobotham/broker-storage-rw
branch
from
August 27, 2026 07:28
ee63980 to
b25a202
Compare
jrobotham-square
marked this pull request as ready for review
August 27, 2026 07:33
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Extends the broker action contract (
buzz-sdk::broker) with six actions in two groups. Four were named as deferred by #6742 (memory read/write,presence.set,typing.set); the other two,observer.emitandliveness.ping, are net-new — the trajectory and keepalive planes the contract never enumerated.Storage — memory read/write (deferred by #6742):
storage.get{ slug }{ value? }—valueabsent = no record (not an error)storage.put{ slug, value }EventPublishedLive signals — the ephemeral signals a running agent emits so an owner and channel can see it work:
presence.set{ status }EventPublishedtyping.set{ channelId }EventPublishedobserver.emit{ frames: [{ kind, payload }] }{ accepted }(batch receipt)liveness.ping{ channelId, turnId }EventPublishedAll six mirror the existing nine in shape, validation, strict-wire rules, and test coverage. The four signal actions are best-effort — a host that doesn't offer one refuses it and the agent carries on.
Why
A keyless agent routes everything through the broker host and holds no relay connection, so anything a local agent emits directly to the relay needs an action or it silently can't happen. #6742 shipped the nine core actions and named four deferrals; this PR:
storage.addresscould address an encrypted-memory record but not read or write one (a keyless runtime was amnesiac across wakes), and presence/typing are how an owner and channel see an agent is alive and composing (the presence dot, the typing bubble);observer.emitandliveness.ping, which broker: define the agent-to-broker action contract #6742 didn't enumerate — the observer/trajectory stream in Buzz Desktop and a turn keepalive. These fell out of the keyless-observability discussion: without them a keyless agent runs invisibly, with no way to surface its tool calls or prove a long turn is still alive.Decisions baked in (all up for discussion)
observer.emitis a batch — trajectory is high-volume, so frames are sent per call and the host re-batches/paces. Each frame is{ kind, payload }wherepayloadis opaque (a serialized frame body the host encrypts verbatim, never parses);kindstays top-level so a host can apply per-kind policy without decrypting. The outcome is a batch receipt{ accepted }, since re-batched frames have no stable per-frame id.liveness.pingis its own action despite overlappingobserver.emiton the wire (a keepalive could be one more frame). Keeping it separate lets a host attach meaning — resetting a stall watchdog — rather than only forwarding it. Flagged in the module docs; happy to collapse it if preferred.typing.sethas no stop counterpart — the indicator is ephemeral and lapses on its own; a client signals by re-sending and stops by falling silent. Thread-scoped typing is a purely-additive later refinement.presence.setreusesbuzz_core::presence::PresenceStatus(online/away/offline) rather than minting a parallel enum.value, nopatch/rm— a client composes those from read-modify-write, asprofile.setalready does.Open questions for review
liveness.pingvsobserver.emit— worth a first-class action, or fold it in?MAX_OBSERVER_FRAMES = 256per call,MAX_OBSERVER_FRAME_BYTES = 64 KiBper frame. Reasonable for trajectory volume?storage.addressfate — with slug-addressed get/put, doesaddressstill earn its place (existence checks / coordinates without a payload)? Left in place either way.Scope / sequencing
Contract crate only — self-contained and mergeable on its own (nothing on
mainmatches these enums exhaustively outsidebuzz-sdk). When the keyless client branch rebases onto this,buzz-cli's exhaustive matches gain the six variants — the forcing function to implement them over the host seam — which is where the client's memory refusals and disabled observer path get removed.Two commits, reviewable independently:
storage.get/storage.put, then the four signals.Verification
cargo test -p buzz-sdk— 302 passed. The self-enforcing coverage, round-trip, null-injection, canonicalization, and exact-schema tests all extend to the new actions automatically.cargo clippy -p buzz-sdk --all-targets -- -D warnings— clean.cargo fmt -p buzz-sdk -- --check— clean.cargo check --workspace— clean.🤖 Generated with Claude Code